package info.ericbutler.microcosm.entities;

import info.ericbutler.microcosm.entities.ui.Item;
import info.ericbutler.microcosm.entities.ui.ItemStack;
import info.ericbutler.microcosm.entities.ui.RecipeManager;
import info.ericbutler.microcosm.enums.AgentState;
import info.ericbutler.microcosm.enums.AnimationName;
import info.ericbutler.microcosm.enums.Direction;
import info.ericbutler.microcosm.game.MicrocosmMain;
import info.ericbutler.microcosm.world.Microcosm;

import java.util.HashMap;
import java.util.Map;
import java.util.Vector;

import org.lwjgl.input.Keyboard;
import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.GameContainer;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Input;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.state.StateBasedGame;

public class Player extends Agent implements UpdatingGameElement {
	private HashMap<AnimationName, Animation>	animations			= new HashMap<>();
	private Rectangle							drawBox				= new Rectangle( 0, 0, 0, 0 ), collisionBox = new Rectangle( 0, 0, 0, 0 );
	private Vector2f							position			= new Vector2f( 0, 0 ), facing = new Vector2f( 0, -1 );
	private float								movementSpeed		= 75f;
	private Animation							currentAnimation	= null;
	private Microcosm							currentWorld;
	private int									collisionFrameX		= 25, collisionFrameY = 33, collisionFrameWidth = 16, collisionFrameHeight = 16;
	private AgentState							state				= AgentState.IDLING;
	private float								harvestSpeed		= 5f;
	private boolean								inventoryOpen		= false;
	private Vector<String>						knownRecipes		= new Vector<String>();
	
	public Player() {
		this( new Vector2f( 0, 0 ) );
	}

	public Player(Vector2f pos) {
		this( pos, 75f );
	}

	public Player(Vector2f pos, float speed) {
		this.position = pos.copy();
		this.movementSpeed = speed;
		this.inventory.setOwner( this );
	}

	@Override
	public void update(GameContainer gc, StateBasedGame sbg, int delta) throws SlickException {
		Input inputs = gc.getInput();
		// check inputs
		readActionInputs( inputs, delta );
		readMenuInputs( inputs, delta );
		// apply changes
		// maintain effects
		// check collisions etc?
		if( this.state == AgentState.ATTACKING ) {
			checkHit( delta );
		}
	}

	@Override
	public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException {
		// Draw me!
		g.drawAnimation( this.getCurrentAnimation(), this.getPosition().x, this.getPosition().y );
		if( MicrocosmMain.DEBUG_DRAW ) {
			g.draw( this.getCollisionBox() );
			g.setColor( Color.yellow );
			g.draw( this.getDrawBox() );
			g.setColor( Color.white );
		}
	}

	public void readMenuInputs(Input inputs, int delta) {
		if( !inventoryOpen && inputs.isKeyPressed( Keyboard.KEY_E ) ) {
			openInventory();
		} else if( inventoryOpen && inputs.isKeyPressed( Keyboard.KEY_E ) ) {
			closeInventory();
		}
	}

	private void openInventory() {
		inventoryOpen = true;
	}
	
	private void closeInventory() {
		inventoryOpen = false;
		MicrocosmMain.MOUSE_ITEM = null;
	}
	
	public void readActionInputs(Input inputs, int delta) {
		Vector2f prevFacing = facing.copy();
		facing.x = 0;
		facing.y = 0;
		
		if( inputs.isKeyDown( Keyboard.KEY_1 ) ) {
			craft("WOODEN_WALL_01");
		}
		if( inputs.isKeyDown( Keyboard.KEY_2 ) ) {
			this.inventory.addItem( new ItemStack( Item.WOODEN_WALL_01, 1 ) );
		}
		
		if( ( !this.state.isBusy() || this.state == AgentState.ATTACKING ) && inputs.isKeyDown( Keyboard.KEY_SPACE ) ) {
			this.state = AgentState.ATTACKING;
		} else {
			this.state = AgentState.IDLING;
		}
		// Movement
		if( inputs.isKeyDown( Keyboard.KEY_W ) ) {
			if( !this.state.isUnturning() ) {
				facing.y -= 1;
			}
			if( !this.state.isStationary() && currentWorld.canMove( Direction.NORTH, this.collisionBox, this.movementSpeed * delta / 1000 ) > 0 ) {
				this.position.y -= currentWorld.canMove( Direction.NORTH, this.collisionBox, this.movementSpeed * delta / 1000 );
				this.state = AgentState.MOVING;
			}
		}
		if( inputs.isKeyDown( Keyboard.KEY_A ) ) {
			if( !this.state.isUnturning() ) {
				facing.x -= 1;
			}
			if( !this.state.isStationary() && currentWorld.canMove( Direction.WEST, this.collisionBox, this.movementSpeed * delta / 1000 ) > 0 ) {
				this.position.x -= currentWorld.canMove( Direction.WEST, this.collisionBox, this.movementSpeed * delta / 1000 );
				this.state = AgentState.MOVING;
			}
		}
		if( inputs.isKeyDown( Keyboard.KEY_S ) ) {
			if( !this.state.isUnturning() ) {
				facing.y += 1;
			}
			if( !this.state.isStationary() && currentWorld.canMove( Direction.SOUTH, this.collisionBox, this.movementSpeed * delta / 1000 ) > 0 ) {
				this.position.y += currentWorld.canMove( Direction.SOUTH, this.collisionBox, this.movementSpeed * delta / 1000 );
				this.state = AgentState.MOVING;
			}
		}
		if( inputs.isKeyDown( Keyboard.KEY_D ) ) {
			if( !this.state.isUnturning() ) {
				facing.x += 1;
			}
			if( !this.state.isStationary() && currentWorld.canMove( Direction.EAST, this.collisionBox, this.movementSpeed * delta / 1000 ) > 0 ) {
				this.position.x += currentWorld.canMove( Direction.EAST, this.collisionBox, this.movementSpeed * delta / 1000 );
				this.state = AgentState.MOVING;
			}
		}
		// Animation determination
		switch( this.state ) {
			case ATTACKING:
				if( facing.x == 0 && facing.y == 0 ) {
					this.setCurrentAnimation( AnimationName.valueOf( "ATTACK_" + this.getCurrentAnimationName().getCardinalDirection().name() ) );
				} else if( facing.x == 0 && facing.y == 1 ) {
					this.setCurrentAnimation( AnimationName.ATTACK_SOUTH );
				} else if( facing.x == 0 && facing.y == -1 ) {
					this.setCurrentAnimation( AnimationName.ATTACK_NORTH );
				} else if( facing.x == 1 && facing.y == 0 ) {
					this.setCurrentAnimation( AnimationName.ATTACK_EAST );
				} else if( facing.x == -1 && facing.y == 0 ) {
					this.setCurrentAnimation( AnimationName.ATTACK_WEST );
				} else if( facing.x != prevFacing.x && facing.y != prevFacing.y ) {
					if( Math.random() <= 0.49d ) {
						this.setCurrentAnimation( facing.x == 1 ? AnimationName.ATTACK_EAST : AnimationName.ATTACK_WEST );
					} else {
						this.setCurrentAnimation( facing.y == 1 ? AnimationName.ATTACK_SOUTH : AnimationName.ATTACK_NORTH );
					}
				} else {
					this.setCurrentAnimation( AnimationName.valueOf( "ATTACK_" + this.getCurrentAnimationName().getCardinalDirection().name() ) );
				}
				break;
			case CRAFTING:
				break;
			case IDLING:
				this.setCurrentAnimation( AnimationName.valueOf( "IDLE_" + this.getCurrentAnimationName().getCardinalDirection().name() ) );
				break;
			case MOVING:
				if( facing.x == 0 && facing.y == 1 ) {
					this.setCurrentAnimation( AnimationName.MOVE_SOUTH );
				} else if( facing.x == 0 && facing.y == -1 ) {
					this.setCurrentAnimation( AnimationName.MOVE_NORTH );
				} else if( facing.x == 1 && facing.y == 0 ) {
					this.setCurrentAnimation( AnimationName.MOVE_EAST );
				} else if( facing.x == -1 && facing.y == 0 ) {
					this.setCurrentAnimation( AnimationName.MOVE_WEST );
				} else if( facing.x != prevFacing.x && facing.y != prevFacing.y ) {
					if( Math.random() <= 0.49d ) {
						this.setCurrentAnimation( facing.x == 1 ? AnimationName.MOVE_EAST : AnimationName.MOVE_WEST );
					} else {
						this.setCurrentAnimation( facing.y == 1 ? AnimationName.MOVE_SOUTH : AnimationName.MOVE_NORTH );
					}
				} else {
					this.setCurrentAnimation( AnimationName.valueOf( "MOVE_" + this.getCurrentAnimationName().getCardinalDirection().name() ) );
				}
				break;
			default:
		}
		// Update drawboxes
		this.getDrawBox().setX( this.position.x );
		this.getDrawBox().setY( this.position.y );
		this.getCollisionBox().setX( this.position.x + collisionFrameX );
		this.getCollisionBox().setY( this.position.y + collisionFrameY );
	}

	private void checkHit(int delta) {
		// TODO: Check for enemy collision hit
		// determine target square
		Vector2f target = this.getWorldCellPosition().add(
				new Vector2f( this.getCurrentAnimationName().getCardinalDirection().adjX(), this.getCurrentAnimationName().getCardinalDirection().adjY() ) );
		// apply dmg based on time attacking it(for farming?) or a specific hitframe (more intuitive for combat)
		if( this.currentWorld.getTiles()[(int)target.y][(int)target.x][0].isFarmable() ) {
			this.currentWorld.getTiles()[(int)target.y][(int)target.x][0].harvest( this, ( (float)delta / 1000 ) * harvestSpeed );
		}
	}

	public void craft(String name) {
		if( RecipeManager.getRecipe( name ) != null && getInventory().hasItems( RecipeManager.getRecipe( name ).getIngredients() ) ) {
			for( ItemStack s : RecipeManager.getRecipe( name ).getIngredients() ) {
				getInventory().removeItem( s );
			}
			getInventory().addItem( RecipeManager.getRecipe( name ).getFinalProduct() );
		}
	}
	
	public Rectangle getDrawBox() {
		return drawBox;
	}

	public Rectangle getCollisionBox() {
		return collisionBox;
	}

	public void setCollisionBox(Rectangle collisionBox) {
		this.collisionBox = collisionBox;
	}

	public Vector2f getPosition() {
		return position;
	}

	public Vector2f getWorldCellPosition() {
		return new Vector2f( (int)( this.collisionBox.getCenterX() / Microcosm.TILE_SCALE_PIXELS ), (int)( this.collisionBox.getCenterY() / Microcosm.TILE_SCALE_PIXELS ) );
	}

	public void setPosition(Vector2f playerPosition) {
		this.position = playerPosition;
	}

	public float getMovementSpeed() {
		return movementSpeed;
	}

	public void setMovementSpeed(float playerSpeed) {
		this.movementSpeed = playerSpeed;
	}

	public AnimationName getCurrentAnimationName() {
		for( Map.Entry<AnimationName, Animation> a : this.animations.entrySet() ) {
			if( a.getValue() == this.currentAnimation ) { return a.getKey(); }
		}
		return null;
	}

	public Animation getCurrentAnimation() {
		return currentAnimation;
	}

	public void setCurrentAnimation(AnimationName name) {
		if( this.currentAnimation != this.animations.get( name ) ) {
			this.currentAnimation.restart();
		}
		this.currentAnimation = this.animations.get( name ) == null ? this.animations.get( AnimationName.IDLE_SOUTH ) : this.animations.get( name );
		this.drawBox.setWidth( this.currentAnimation.getWidth() );
		this.drawBox.setHeight( this.currentAnimation.getHeight() );
		this.collisionBox.setWidth( collisionFrameWidth );
		this.collisionBox.setHeight( collisionFrameHeight );
	}

	public void addAnimation(AnimationName name, Animation sheet) {
		this.addAnimation( name, sheet, true, true, 0 );
	}

	public void addAnimation(AnimationName name, Animation sheet, boolean autoUpdate, boolean isLooping, int startFrame) {
		sheet.setAutoUpdate( autoUpdate );
		sheet.setCurrentFrame( startFrame );
		sheet.setLooping( isLooping );
		this.animations.put( name, sheet );
		if( currentAnimation == null ) {
			currentAnimation = this.animations.get( name );
		}
	}

	public void learnRecipe(String name) {
		if( !this.knownRecipes.contains( name ) ) {
			this.knownRecipes.add( name );
		}
	}
	
	public void unlearnRecipe(String name) {
		this.knownRecipes.remove( name );
	}
	
	public void unlearnAllRecipes() {
		this.knownRecipes.clear();
	}
	
	public Animation getAnimation(AnimationName name) {
		return this.animations.get( name );
	}

	public Animation removeAnimation(AnimationName name) {
		return this.animations.remove( name );
	}

	public void Animations() {
		this.animations.clear();
	}

	@Override
	public Vector2f getCenter() {
		return new Vector2f( this.position.x + this.currentAnimation.getCurrentFrame().getWidth() / 2, this.position.y
				+ this.currentAnimation.getCurrentFrame().getHeight() / 2 );
	}

	public Microcosm getCurrentWorld() {
		return currentWorld;
	}

	public void setCurrentWorld(Microcosm currentWorld) {
		this.currentWorld = currentWorld;
	}

	public boolean isInventoryOpen() {
		return inventoryOpen;
	}

	public void setInventoryOpen(boolean inventoryOpen) {
		this.inventoryOpen = inventoryOpen;
	}

	public Vector2f getFacing() {
		return facing;
	}

	public void setFacing(Vector2f facing) {
		this.facing = facing;
	}

	public int getCollisionFrameX() {
		return collisionFrameX;
	}

	public void setCollisionFrameX(int collisionFrameX) {
		this.collisionFrameX = collisionFrameX;
	}

	public int getCollisionFrameY() {
		return collisionFrameY;
	}

	public void setCollisionFrameY(int collisionFrameY) {
		this.collisionFrameY = collisionFrameY;
	}

	public AgentState getState() {
		return state;
	}

	public void setState(AgentState state) {
		this.state = state;
	}

	public float getHarvestSpeed() {
		return harvestSpeed;
	}

	public void setHarvestSpeed(float harvestSpeed) {
		this.harvestSpeed = harvestSpeed;
	}
}
